/**
 * IconManager.java 2 mai 2006
 * 
 * Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>. All Rights
 * Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.windu2b.jcaddie.swing;

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;

import javax.imageio.*;
import javax.swing.*;

import org.windu2b.jcaddie.model.*;
import org.windu2b.jcaddie.tools.*;

/**
 * Singleton managing icons cache.
 * 
 * @author Emmanuel Puybaret, windu.2b
 */
public class IconManager
{
	private static IconManager	        instance;


	// Icon used if an image content couldn't be loaded
	private Content	                    errorIconContent;


	// Icon used while an image content is loaded
	private Content	                    waitIconContent;


	// Icon used when a category has no personal image
	private Content	                    defaultCategoryIcon;


	// Map storing loaded icons
	private Map<ContentHeightKey, Icon>	icons;


	private Executor	                iconsLoader;




	/**
	 * Returns an instance of this singleton.
	 */
	private IconManager()
	{
		errorIconContent = new URLContent( ImageInJAR.getInstance().getImage(
		        URLIcon.ERROR.toString() ) );
		waitIconContent = new URLContent( ImageInJAR.getInstance().getImage(
		        URLIcon.WAIT.toString() ) );
		defaultCategoryIcon = new URLContent( ImageInJAR.getInstance()
		        .getImage( URLIcon.DEFAULT_CATEGORY_ICON.toString() ) );
		
		iconsLoader = Executors.newFixedThreadPool( 5 );
		icons = new HashMap<ContentHeightKey, Icon>();
	}




	public static IconManager getInstance()
	{
		if ( instance == null ) instance = new IconManager();

		return instance;
	}




	/**
	 * Returns an icon read from <code>content</code> and rescaled at a given
	 * <code>height</code>.
	 * 
	 * @param content
	 *            an objet containing an image
	 * @param height
	 *            the desired height of the returned icon
	 * @param waitingComponent
	 *            a waiting component
	 * @return L'icône désignée par <code>content</code> et à l'échelle
	 *         <code>height</code>
	 */
	public Icon getIcon( Content content, int height, Component waitingComponent )
	{
		ContentHeightKey contentKey = new ContentHeightKey( content, height );
		Icon icon = icons.get( contentKey );

		if ( icon == null )
		{
			if ( content == errorIconContent || content == waitIconContent )
			{
				icon = createIcon( content, height, waitingComponent, null );
			}
			else
			{
				icon = new IconProxy( content, height, waitingComponent,
				        getIcon( errorIconContent, height, null ), getIcon(
				                waitIconContent, height, null ) );
			}

			icons.put( contentKey, icon );
		}

		return icon;
	}




	/**
	 * Returns an icon created and scaled from the content of contentKey.
	 * 
	 * @param content
	 *            the content from which the icon image is read
	 * @param height
	 *            the desired height of the returned icon
	 * @param waitingComponent
	 *            a waiting component
	 * @param errorIcon
	 *            the returned icon in cas of error
	 */
	private Icon createIcon( Content content, int height,
	        Component waitingComponent, Icon errorIcon )
	{
		try
		{
			InputStream contentStream = content.openStream();
			BufferedImage image = ImageIO.read( contentStream );
			contentStream.close();

			if ( image != null )
			{
				int width = image.getWidth() * height / image.getHeight();
				Image scaledImage = image.getScaledInstance( width, height,
				        Image.SCALE_SMOOTH );

				return new ImageIcon( scaledImage );
			}
		}
		catch ( IOException ex )
		{
		}

		return errorIcon;
	}



	/**
	 * @author Emmanuel Puybaret, windu.2b Proxy icon that displays a temporary
	 *         icon while waiting image loading completion.
	 */
	private class IconProxy implements Icon
	{
		private Icon	icon;




		public IconProxy( final Content content, final int height,
		        final Component waitingComponent, final Icon errorIcon,
		        Icon waitIcon )
		{
			this.icon = waitIcon;

			iconsLoader.execute( new Runnable()
			{

				public void run()
				{
					icon = createIcon( content, height, waitingComponent,
					        errorIcon );
					waitingComponent.repaint();
				}

			} );
		}




		/**
		 * @see javax.swing.Icon#getIconWidth()
		 */
		public int getIconWidth()
		{
			return icon.getIconWidth();
		}




		/**
		 * @see javax.swing.Icon#getIconHeight()
		 */
		public int getIconHeight()
		{
			return icon.getIconHeight();
		}




		/**
		 * @see javax.swing.Icon#paintIcon(java.awt.Component,
		 *      java.awt.Graphics, int, int)
		 */
		public void paintIcon( Component c, Graphics g, int x, int y )
		{
			icon.paintIcon( c, g, x, y );
		}
	}

	/**
	 * @author Emmanuel Puybaret, windu.2b Key used to access to icons.
	 */
	private static final class ContentHeightKey
	{
		private final Content	content;


		private final int		height;




		public ContentHeightKey( Content content, int height )
		{
			this.content = content;
			this.height = height;
		}




		@Override
		public boolean equals( Object obj )
		{
			ContentHeightKey key = ( ContentHeightKey ) obj;

			return content == key.content && height == key.height;
		}




		@Override
		public int hashCode()
		{
			return content.hashCode() + height;
		}
	}




	/**
	 * @return L'icône par défaut à utiliser, si la catégorie n'a pas défini
	 *         d'icône personnalisée
	 */
	public Content getDefaultCategoryIcon()
	{
		return defaultCategoryIcon;
	}
}